/**
 * @file
 * @brief RTD：XWDS：UART
 * @author
 * + 隐星魂 (Roy Sun) <xwos@xwos.tech>
 * @copyright
 * + Copyright © 2015 xwos.tech, All Rights Reserved.
 * > Licensed under the Apache License, Version 2.0 (the "License");
 * > you may not use this file except in compliance with the License.
 * > You may obtain a copy of the License at
 * >
 * >         http://www.apache.org/licenses/LICENSE-2.0
 * >
 * > Unless required by applicable law or agreed to in writing, software
 * > distributed under the License is distributed on an "AS IS" BASIS,
 * > WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * > See the License for the specific language governing permissions and
 * > limitations under the License.
 */

#include "../board/std.h"
#include <string.h>
#include <xwos/osal/irq.h>
#include <xwos/osal/sync/sem.h>
#include <xwos/osal/sync/cond.h>
#include <xwos/osal/lock/spinlock.h>
#include <xwcd/ds/uart/controller.h>
#include "Mcl.h"
#include "Uart.h"

struct mcalxwds_lpuart_drvdata {
        uint8 channel;
        LPUART_Type * regbase;
        struct {
                struct xwos_splk lock;
                struct xwos_cond completion;
                Uart_StatusType state;
                struct xwos_sem available;
                xwu8_t * eqbuf;
                xwsz_t eqbuf_size;
                xwds_uartc_eqcb_f eqcb;
        } tx;
};

static
xwer_t mcalxwds_lpuart_drv_probe(struct xwds_device * dev);

static
xwer_t mcalxwds_lpuart_drv_remove(struct xwds_device * dev);

static
xwer_t mcalxwds_lpuart_drv_start(struct xwds_device * dev);

static
xwer_t mcalxwds_lpuart_drv_stop(struct xwds_device * dev);

#if defined(XWCDCFG_ds_PM) && (1 == XWCDCFG_ds_PM)
static
xwer_t mcalxwds_lpuart_drv_resume(struct xwds_device * dev);

static
xwer_t mcalxwds_lpuart_drv_suspend(struct xwds_device * dev);
#endif

static
xwer_t mcalxwds_lpuart_drv_cfg(struct xwds_uartc * uartc,
                               const struct xwds_uart_cfg * cfg);

static
xwer_t mcalxwds_lpuart_drv_tx(struct xwds_uartc * uartc,
                              const xwu8_t * data, xwsz_t * size,
                              xwtm_t to);

static
xwer_t mcalxwds_lpuart_drv_putc(struct xwds_uartc * uartc,
                                const xwu8_t byte);

static
xwer_t mcalxwds_lpuart_drv_eq(struct xwds_uartc * uartc,
                              const xwu8_t * data, xwsz_t * size,
                              xwds_uartc_eqcb_f cb);

const struct xwds_uartc_driver mcalxwds_lpuart_drv = {
        .base = {
                .name = "mcalxwds.uart",
                .probe = mcalxwds_lpuart_drv_probe,
                .remove = mcalxwds_lpuart_drv_remove,
                .start = mcalxwds_lpuart_drv_start,
                .stop = mcalxwds_lpuart_drv_stop,
#if defined(XWCDCFG_ds_PM) && (1 == XWCDCFG_ds_PM)
                .suspend = mcalxwds_lpuart_drv_suspend,
                .resume =  mcalxwds_lpuart_drv_resume,
#endif
        },
        .cfg = mcalxwds_lpuart_drv_cfg,
        .tx = mcalxwds_lpuart_drv_tx,
        .putc = mcalxwds_lpuart_drv_putc,
        .eq = mcalxwds_lpuart_drv_eq,
};

/******** ******** uart2 ******** ********/
__xwcc_alignl1cache xwu8_t mcalxwds_uart2_eqbuffer[XWLIBCFG_XWLOG_BUFSIZE] = {0};

struct mcalxwds_lpuart_drvdata mcalxwds_uart2_drvdata = {
        .channel = 0,
        .regbase = IP_LPUART_2,
        .tx = {
                .eqbuf = mcalxwds_uart2_eqbuffer,
                .eqbuf_size = sizeof(mcalxwds_uart2_eqbuffer),
        },
};

struct xwds_uartc mcalxwds_uart2 = {
        /* attributes */
        .dev = {
                .name = "mcalxwds.uart",
                .id = 2,
                .resources = NULL,
                .drv = xwds_cast(struct xwds_driver *, &mcalxwds_lpuart_drv),
                .data = (void *)&mcalxwds_uart2_drvdata,
        },
        .cfg = NULL,
};

/******** ******** devices ******** ********/
struct xwds_uartc * mcalxwds_lpuart_devices[] = {
        [0] = &mcalxwds_uart2,
        [1] = NULL,
        [2] = NULL,
        [3] = NULL,
        [4] = NULL,
        [5] = NULL,
        [6] = NULL,
        [7] = NULL,
};

/******** ******** driver functions ******** ********/
static
xwer_t mcalxwds_lpuart_drv_probe(struct xwds_device * dev)
{
        struct xwds_uartc * uartc;
        struct mcalxwds_lpuart_drvdata * drvdata;

        uartc = xwds_cast(struct xwds_uartc *, dev);
        drvdata = uartc->dev.data;
        drvdata->tx.eqcb = NULL;
        xwos_splk_init(&drvdata->tx.lock);
        xwos_cond_init(&drvdata->tx.completion);
        xwos_sem_init(&drvdata->tx.available, 1, 1);
        return XWOK;
}

static
xwer_t mcalxwds_lpuart_drv_remove(struct xwds_device * dev)
{
        struct xwds_uartc * uartc;
        struct mcalxwds_lpuart_drvdata * drvdata;

        uartc = xwds_cast(struct xwds_uartc *, dev);
        drvdata = uartc->dev.data;
        xwos_sem_fini(&drvdata->tx.available);
        xwos_cond_fini(&drvdata->tx.completion);
        return XWOK;
}

static
xwer_t mcalxwds_lpuart_drv_start(struct xwds_device * dev)
{
        struct xwds_uartc * uartc;
        struct mcalxwds_lpuart_drvdata * drvdata;
        Std_ReturnType stdrc;
        xwer_t rc;

        uartc = xwds_cast(struct xwds_uartc *, dev);
        drvdata = uartc->dev.data;
        xwds_uartc_drvcb_rxq_flush(uartc);
        stdrc = Uart_AsyncReceive(drvdata->channel,
                                  &uartc->rxq.mem[0],
                                  sizeof(uartc->rxq.mem));
        if (E_OK == stdrc) {
                rc = XWOK;
        } else {
                rc = -EIO;
        }
        return rc;
}

static
xwer_t mcalxwds_lpuart_drv_stop(struct xwds_device * dev)
{
        struct xwds_uartc * uartc;
        struct mcalxwds_lpuart_drvdata * drvdata;
        xwu32_t remaining;
        xwreg_t cpuirq;

        uartc = xwds_cast(struct xwds_uartc *, dev);
        drvdata = uartc->dev.data;
        Uart_Abort(drvdata->channel, UART_SEND);
        xwos_splk_lock_cpuirqsv(&drvdata->tx.lock, &cpuirq);
        drvdata->tx.state = Uart_GetStatus(drvdata->channel, &remaining, UART_SEND);
        xwos_splk_unlock_cpuirqrs(&drvdata->tx.lock, cpuirq);
        xwos_cond_broadcast(&drvdata->tx.completion);

        Uart_Abort(drvdata->channel, UART_RECEIVE);
        return XWOK;
}

#if defined(XWCDCFG_ds_PM) && (1 == XWCDCFG_ds_PM)
static
xwer_t mcalxwds_lpuart_drv_resume(struct xwds_device * dev)
{
        return mcalxwds_lpuart_drv_start(dev);
}

static
xwer_t mcalxwds_lpuart_drv_suspend(struct xwds_device * dev)
{
        return mcalxwds_lpuart_drv_stop(dev);
}
#endif

static
xwer_t mcalxwds_lpuart_drv_cfg(struct xwds_uartc * uartc,
                               const struct xwds_uart_cfg * cfg)
{
        XWOS_UNUSED(uartc);
        XWOS_UNUSED(cfg);
        return -ENOSYS;
}

static
xwer_t mcalxwds_lpuart_drv_tx(struct xwds_uartc * uartc,
                              const xwu8_t * data, xwsz_t * size,
                              xwtm_t to)
{
        struct mcalxwds_lpuart_drvdata * drvdata;
        Std_ReturnType stdrc;
        xwsz_t wrsz;
        xwu32_t remaining;
        union xwos_ulock lock;
        xwsq_t lkst;
        xwreg_t cpuirq;
        xwer_t rc;

        drvdata = uartc->dev.data;
        rc = xwos_sem_wait_to(&drvdata->tx.available, to);
        if (rc < 0) {
                goto err_sem_wait;
        }

        lock.osal.splk = &drvdata->tx.lock;
        wrsz = *size;
#if defined(CPUCFG_DCACHE) && (1 == CPUCFG_DCACHE)
        xwu32_t org = XWBOP_ROUND((xwu32_t)data,
                                  CPUCFG_L1_CACHELINE_SIZE);
        xwu32_t end = XWBOP_ALIGN((xwu32_t)data + (xwu32_t)wrsz,
                                  CPUCFG_L1_CACHELINE_SIZE);
        Mcl_CacheCleanByAddr(MCL_CACHE_DATA, false, org, end - org);
#endif
        xwos_splk_lock_cpuirqsv(&drvdata->tx.lock, &cpuirq);
        stdrc = Uart_AsyncSend(drvdata->channel, data, wrsz);
        if (E_OK == stdrc) {
                drvdata->tx.state = Uart_GetStatus(drvdata->channel, &remaining,
                                                   UART_SEND);
                rc = xwos_cond_wait_to(&drvdata->tx.completion, lock, XWOS_LK_SPLK,
                                       NULL, to, &lkst);
                if (XWOK == rc) {
                        XWOS_BUG_ON((xwsq_t)XWOS_LKST_UNLOCKED == lkst);
                        *size = wrsz - remaining;
                        if (UART_STATUS_NO_ERROR == drvdata->tx.state) {
                                rc = XWOK;
                        } else if (UART_STATUS_ABORTED == drvdata->tx.state) {
                                rc = -ECANCELED;
                        } else {
                                rc =-EIO;
                        }
                        xwos_splk_unlock_cpuirqrs(&drvdata->tx.lock, cpuirq);
                } else {
                        if ((xwsq_t)XWOS_LKST_LOCKED == lkst) {
                                xwos_splk_unlock_cpuirqrs(&drvdata->tx.lock, cpuirq);
                        } else {
                                xwos_cpuirq_restore_lc(cpuirq);
                        }
                }
        } else {
                xwos_splk_unlock_cpuirqrs(&drvdata->tx.lock, cpuirq);
                xwos_sem_post(&drvdata->tx.available);
                *size = 0;
                rc = -EIO;
        }

err_sem_wait:
        return rc;
}

static
xwer_t mcalxwds_lpuart_drv_eq(struct xwds_uartc * uartc,
                              const xwu8_t * data, xwsz_t * size,
                              xwds_uartc_eqcb_f cb)
{
        struct mcalxwds_lpuart_drvdata * drvdata;
        Std_ReturnType stdrc;
        xwu32_t remaining;
        xwreg_t cpuirq;
        xwsz_t wrsz;
        xwer_t rc;

        drvdata = uartc->dev.data;
        if ((NULL == drvdata->tx.eqbuf) || (drvdata->tx.eqbuf_size == 0)) {
                rc = -ENOSYS;
                goto err_nosys;
        }
        rc = xwos_sem_trywait(&drvdata->tx.available);
        if (rc < 0) {
                rc = -EBUSY;
                goto err_busy;
        }
        wrsz = *size > drvdata->tx.eqbuf_size ? drvdata->tx.eqbuf_size : *size;
        memcpy(drvdata->tx.eqbuf, data, wrsz);
#if defined(CPUCFG_DCACHE) && (1 == CPUCFG_DCACHE)
        xwu32_t org = XWBOP_ROUND((xwu32_t)drvdata->tx.eqbuf,
                                  CPUCFG_L1_CACHELINE_SIZE);
        xwu32_t end = XWBOP_ALIGN((xwu32_t)drvdata->tx.eqbuf + (xwu32_t)wrsz,
                                  CPUCFG_L1_CACHELINE_SIZE);
        Mcl_CacheCleanByAddr(MCL_CACHE_DATA, false, org, end - org);
#endif
        xwos_splk_lock_cpuirqsv(&drvdata->tx.lock, &cpuirq);
        drvdata->tx.eqcb = cb;
        stdrc = Uart_AsyncSend(drvdata->channel, drvdata->tx.eqbuf, wrsz);
        if (E_OK == stdrc) {
                drvdata->tx.state = Uart_GetStatus(drvdata->channel, &remaining,
                                                   UART_SEND);
                xwos_splk_unlock_cpuirqrs(&drvdata->tx.lock, cpuirq);
                *size = wrsz;
                rc = XWOK;
        } else {
                drvdata->tx.eqcb = NULL;
                xwos_splk_unlock_cpuirqrs(&drvdata->tx.lock, cpuirq);
                xwos_sem_post(&drvdata->tx.available);
                *size = 0;
                rc = -EIO;
        }

err_busy:
err_nosys:
        return rc;
}

static
xwer_t mcalxwds_lpuart_drv_putc(struct xwds_uartc * uartc,
                                const xwu8_t byte)
{
        struct mcalxwds_lpuart_drvdata * drvdata;

        drvdata = uartc->dev.data;
        Lpuart_Uart_Ip_Putchar(drvdata->regbase, byte);
        return XWOK;
}

/******** ******** Lpuart Callbacke ******** ********/
void mcalxwds_lpuart_txcb_complete(struct xwds_uartc * uartc, uint8 channel)
{
        struct mcalxwds_lpuart_drvdata * drvdata;
        xwu32_t remaining;
        xwreg_t cpuirq;
        xwds_uartc_eqcb_f cb = NULL;
        xwer_t cbrc;

        XWOS_UNUSED(channel);
        drvdata = uartc->dev.data;
        xwos_splk_lock_cpuirqsv(&drvdata->tx.lock, &cpuirq);
        drvdata->tx.state = Uart_GetStatus(channel, &remaining, UART_SEND);
        switch (drvdata->tx.state) {
        case UART_STATUS_NO_ERROR:
                cbrc = XWOK;
                break;
        case UART_STATUS_ABORTED:
                cbrc = -ECANCELED;
                break;
        default:
                cbrc = -EIO;
                break;
        }
        cb = drvdata->tx.eqcb;
        drvdata->tx.eqcb = NULL;
        xwos_splk_unlock_cpuirqrs(&drvdata->tx.lock, cpuirq);
        xwos_cond_broadcast(&drvdata->tx.completion);
        if (NULL != cb) {
                cb(uartc, cbrc);
        }
        xwos_sem_post(&drvdata->tx.available);
}

void mcalxwds_lpuart_rxcb_complete(struct xwds_uartc * uartc, uint8 channel)
{
#if defined(CPUCFG_DCACHE) && (1 == CPUCFG_DCACHE)
        Mcl_CacheInvalidateByAddr(MCL_CACHE_DATA,
                                  (xwu32_t)uartc->rxq.mem,
                                  (xwu32_t)sizeof(uartc->rxq.mem));
#endif
        xwds_uartc_drvcb_rxq_pub(uartc, 0);
        Uart_SetBuffer(channel, uartc->rxq.mem, sizeof(uartc->rxq.mem), UART_RECEIVE);
}

void mcalxwds_lpuart_rxcb_recirculating(struct xwds_uartc * uartc, uint8 channel)
{
        XWOS_UNUSED(channel);
#if defined(CPUCFG_DCACHE) && (1 == CPUCFG_DCACHE)
        Mcl_CacheInvalidateByAddr(MCL_CACHE_DATA,
                                  (xwu32_t)uartc->rxq.mem,
                                  (xwu32_t)sizeof(uartc->rxq.mem));
#endif
        xwds_uartc_drvcb_rxq_pub(uartc, 0);
}

void mcalxwds_lpuart_rxcb_idle(struct xwds_uartc * uartc, uint8 channel)
{
        xwu32_t rest;
        xwsq_t tail;
        xwreg_t cpuirq;

#if defined(CPUCFG_DCACHE) && (1 == CPUCFG_DCACHE)
        Mcl_CacheInvalidateByAddr(MCL_CACHE_DATA,
                                  (xwu32_t)uartc->rxq.mem,
                                  (xwu32_t)sizeof(uartc->rxq.mem));
#endif
        xwos_cpuirq_save_lc(&cpuirq);
        Uart_GetStatus(channel, &rest, UART_RECEIVE);
        if (rest > 0) {
                tail = sizeof(uartc->rxq.mem) - rest;
                xwds_uartc_drvcb_rxq_pub(uartc, tail);
        }
        xwos_cpuirq_restore_lc(cpuirq);
}

void mcalxwds_lpuart_rxcb_end(struct xwds_uartc * uartc, uint8 channel)
{
        Uart_StatusType state;
        xwu32_t remaining;

        state = Uart_GetStatus(channel, &remaining, UART_RECEIVE);
        switch (state) {
        case UART_STATUS_RX_OVERRUN_ERROR:
        case UART_STATUS_FRAMING_ERROR:
        case UART_STATUS_PARITY_ERROR:
        case UART_STATUS_NOISE_ERROR:
        case UART_STATUS_DMA_ERROR:
                xwds_uartc_drvcb_rxq_flush(uartc);
                Uart_AsyncReceive(channel,
                                  &uartc->rxq.mem[0],
                                  sizeof(uartc->rxq.mem));
                break;
        case UART_STATUS_NO_ERROR:
        case UART_STATUS_OPERATION_ONGOING:
        case UART_STATUS_TIMEOUT:
        case UART_STATUS_ABORTED:
                break;
        default:
                break;
        }
}

void mcalxwds_lpuart_callback(uint8 channel, Uart_EventType event)
{
        struct xwds_uartc * uartc;

        uartc = mcalxwds_lpuart_devices[channel];
        switch (event) {
        case UART_EVENT_RX_FULL:
                mcalxwds_lpuart_rxcb_complete(uartc, channel);
                break;
        case UART_EVENT_RX_END:
                mcalxwds_lpuart_rxcb_end(uartc, channel);
                break;
        case UART_EVENT_RX_IDLE:
                mcalxwds_lpuart_rxcb_idle(uartc, channel);
                break;
        case UART_EVENT_RX_ERROR:
                break;
        case UART_EVENT_RX_RECIRCULATING:
                mcalxwds_lpuart_rxcb_recirculating(uartc, channel);
                break;
        case UART_EVENT_TX_EMPTY:
                break;
        case UART_EVENT_TX_END:
                mcalxwds_lpuart_txcb_complete(uartc, channel);
                break;
        case UART_EVENT_TX_ERROR:
                break;
        default:
                break;
        }
}
